﻿//QuikTrader.cs
//Copyright (c) 2013 StockSharp LLC, all rights reserved.
//This code module is part of StockSharp library.
//This code is licensed under the GNU GENERAL PUBLIC LICENSE Version 3.
//See the file License.txt for the license details.
//More info on: http://stocksharp.com

namespace StockSharp.Quik
{
	using System;
	using System.Collections.Generic;
	using System.Diagnostics;
	using System.IO;
	using System.Linq;
	using System.Text.RegularExpressions;
	using System.Threading;

	using Ecng.ComponentModel;
	using Ecng.Collections;
	using Ecng.Common;
	using Ecng.Interop;
	using Ecng.Serialization;

	using StockSharp.Algo.Storages;
	using StockSharp.BusinessEntities;
	using StockSharp.Algo;
	using StockSharp.Quik.Properties;
	using StockSharp.Logging;

	/// <summary>
	/// Реализация интерфейса <see cref="ITrader"/>, предоставляющая шлюз взаимодействия с системой Quik.
	/// </summary>
	public class QuikTrader : BaseTrader
	{
		private static readonly SynchronizedSet<string> _terminalPaths = new SynchronizedSet<string>();

		private readonly SynchronizedDictionary<long, Transaction> _transactions = new SynchronizedDictionary<long, Transaction>();
		private readonly SynchronizedMultiDictionary<Order, Transaction> _orderTransactions = new SynchronizedMultiDictionary<Order, Transaction>();
		private readonly SynchronizedDictionary<Security, SynchronizedDictionary<DdeTableColumn, object>> _securityChanges = new SynchronizedDictionary<Security, SynchronizedDictionary<DdeTableColumn, object>>();

		private readonly Regex _changeOrdersRegex = new Regex(@"ID:(?<order>\s\d+)", RegexOptions.Compiled);

		private readonly List<string> _eveningClasses = new List<string>();
		private readonly Dictionary<string, Security> _rtsstSecurities = new Dictionary<string, Security>();

		private static int _counter;

		/// <summary>
		/// Создать <see cref="QuikTrader"/>.
		/// </summary>
		public QuikTrader()
			: this(QuikTerminal.GetDefaultPath())
		{
		}

		/// <summary>
		/// Создать <see cref="QuikTrader"/>.
		/// </summary>
		/// <param name="path">Путь к директории, где установлен Quik (или путь к файлу info.exe).</param>
		public QuikTrader(string path)
			: base(Platforms.x86)
		{
			SecurityClassInfo.Add("SPBOPT", new RefPair<SecurityTypes, ExchangeBoard>(SecurityTypes.Option, ExchangeBoard.Forts));
			SecurityClassInfo.Add("SPBFUT", new RefPair<SecurityTypes, ExchangeBoard>(SecurityTypes.Future, ExchangeBoard.Forts));

			// http://stocksharp.com/forum/yaf_postsm11628_Pozitsii-po-dierivativam.aspx#post11628
			SecurityClassInfo.Add("OPTUX", new RefPair<SecurityTypes, ExchangeBoard>(SecurityTypes.Option, ExchangeBoard.Ux));
			SecurityClassInfo.Add("FUTUX", new RefPair<SecurityTypes, ExchangeBoard>(SecurityTypes.Future, ExchangeBoard.Ux));
			SecurityClassInfo.Add("GTS", new RefPair<SecurityTypes, ExchangeBoard>(SecurityTypes.Stock, ExchangeBoard.Ux));

			// http://groups.google.ru/group/stocksharp/msg/28518b814c925521
			SecurityClassInfo.Add("RTSST", new RefPair<SecurityTypes, ExchangeBoard>(SecurityTypes.Stock, ExchangeBoard.Forts));

			// http://stocksharp.com/forum/yaf_postst1166_Probliema-s-pierienosom-zaiavok-s-viechierniei-siessii.aspx
			_eveningClasses.Add("FUTEVN");
			_eveningClasses.Add("OPTEVN");

			CreateTables();

			CustomTableDeserializer = new DdeCustomTableDeserializer();

			IsAsyncMode = true;

			Path = path;
			DllName = "TRANS2QUIK.DLL";

			var ddeServer = "STOCKSHARP";

			if (Interlocked.Increment(ref _counter) > 1)
				ddeServer += _counter;

			DdeServer = ddeServer;
		}

		/// <summary>
		/// Поддерживает ли Quik Единую Денежную Позицию.
		/// </summary>
		/// <remarks>
		/// False по умолчанию.
		/// </remarks>
		public bool IsCommonMonetaryPosition { get; set; }

		private string _dllName;

		/// <summary>
		/// Имя dll-файла, содержащее Quik API. По-умолчанию равно TRANS2QUIK.DLL.
		/// </summary>
		public string DllName
		{
			get { return _dllName; }
			set
			{
				if (value.IsEmpty())
					throw new ArgumentNullException("value");

				if (value != _dllName)
				{
					Api = null;
					_dllName = value;
				}
			}
		}

		private string _ddeServer;

		/// <summary>
		/// Название DDE сервера. По-умолчанию равно STOCKSHARP.
		/// </summary>
		public string DdeServer
		{
			get { return _ddeServer; }
			set
			{
				if (value.IsEmpty())
					throw new ArgumentNullException("value");

				if (DdeServer == value)
					return;

				if (IsExportStarted)
					throw new InvalidOperationException("Нельзя менять DDE сервер при запущенном экспорте.");

				DisposeDdeServer();
				_ddeServer = value;
			}
		}

		private readonly object _quikDdeServerLock = new object();
		private QuikDdeServer _quikDdeServer;

		private QuikDdeServer QuikDdeServer
		{
			get
			{
				lock (_quikDdeServerLock)
				{
					if (_quikDdeServer == null)
					{
						_quikDdeServer = new QuikDdeServer(DdeServer);
						_quikDdeServer.Poke += OnPoke;
						_quikDdeServer.Error += RaiseProcessDataError;
					}
				}
				
				return _quikDdeServer;
			}
		}

		private string _path;

		/// <summary>
		/// Путь к директории, где установлен Quik (или путь к файлу info.exe). По-умолчанию равно <see cref="QuikTerminal.GetDefaultPath"/>.
		/// </summary>
		public string Path
		{
			get { return _path; }
			set
			{
				if (Path == value)
					return;

				Terminal = null;
				Api = null;

				_path = value;
			}
		}

		/// <summary>
		/// Текстовое описание шлюза.
		/// </summary>
		public override string DisplayName
		{
			get { return Path; }
		}

		private ApiWrapper _api;

		private ApiWrapper Api
		{
			get
			{
				if (_api == null)
				{
					if (!File.Exists(DllName))
					{
						var useNew = GetTerminal().Version >= "5.15.0.0".To<Version>();

						DllName.CreateDirIfNotExists();

						File.WriteAllBytes(DllName, useNew ? Resources.TRANS2QUIK_11 : Resources.TRANS2QUIK_10);
					}

					_api = new ApiWrapper(DllName);
					_api.ConnectionChanged += OnConnectionChanged;
					_api.OrderReply += OnOrderReply;
					_api.TradeReply += OnTradeReply;
					_api.TransactionReply += OnTransactionReply;
				}

				return _api;
			}
			set
			{
				if (_api == value)
					return;

				if (_api != null)
				{
					_api.ConnectionChanged -= OnConnectionChanged;
					_api.OrderReply -= OnOrderReply;
					_api.TradeReply -= OnTradeReply;
					_api.TransactionReply -= OnTransactionReply;
					_api.Dispose();
				}

				_api = value;
			}
		}

		private QuikTerminal _terminal;

		/// <summary>
		/// Вспомогательный класс для управления терминалом Quik.
		/// </summary>
		public QuikTerminal Terminal
		{
			get
			{
				if (Path.IsEmpty())
					return null;

				if (_terminal == null)
				{
					_terminal = QuikTerminal.Get(Path);
					_terminal.Trader = this;
				}

				return _terminal;
			}
			private set
			{
				if (_terminal == value)
					return;

				if (_terminal != null)
				{
					_terminalPaths.Remove(_terminal.FileName);

					_terminal.Trader = null;
					_terminal = null;

					Api = null;
				}

				if (value != null)
				{
					if (!_terminalPaths.TryAdd(value.FileName))
						throw new InvalidOperationException("Quik шлюз для {0} уже зарегистрирован.".Put(value.FileName));

					_terminal = value;
					_terminal.Trader = this;
				}
			}
		}

		private QuikTerminal GetTerminal()
		{
			if (Path.IsEmpty())
				throw new InvalidOperationException("Путь к директории, где установлен Quik, не указан.");

			return Terminal;
		}

		private void CreateTables()
		{
			SecuritiesTable = new DdeTable(DdeTableTypes.Security, "инструменты", new[]
			{
				DdeSecurityColumns.Name,
				DdeSecurityColumns.Code,
				DdeSecurityColumns.Class,
				DdeSecurityColumns.Status,
				DdeSecurityColumns.MinLotSize,
				DdeSecurityColumns.MinStepSize
			});

			SecuritiesChangeTable = new DdeTable(DdeTableTypes.Security, "инструменты (изменения)", new[]
			{
				DdeSecurityColumns.LastChangeTime,
				DdeSecurityColumns.Code,
				DdeSecurityColumns.Class
			});

			TradesTable = new DdeTable(DdeTableTypes.Trade, "все сделки", new[]
			{
				DdeTradeColumns.Id,
				DdeTradeColumns.Time,
				DdeTradeColumns.SecurityCode,
				DdeTradeColumns.SecurityClass,
				DdeTradeColumns.Price,
				DdeTradeColumns.Volume,
				DdeTradeColumns.OrderDirection,
				DdeTradeColumns.Date
			});

			OrdersTable = new DdeTable(DdeTableTypes.Order, "заявки", new[]
			{
				DdeOrderColumns.Id,
				DdeOrderColumns.SecurityCode,
				DdeOrderColumns.SecurityClass,
				DdeOrderColumns.Price,
				DdeOrderColumns.Volume,
				DdeOrderColumns.Balance,
				DdeOrderColumns.Direction,
				DdeOrderColumns.State,
				DdeOrderColumns.Time,
				DdeOrderColumns.CancelTime,
				DdeOrderColumns.Account,
				DdeOrderColumns.Type,
				DdeOrderColumns.ExpiryDate,
				DdeOrderColumns.Comment,
				DdeOrderColumns.TransactionId,
				DdeOrderColumns.Date,
				DdeOrderColumns.ClientCode
			});

			StopOrdersTable = new DdeTable(DdeTableTypes.StopOrder, "стоп-заявки", new[]
			{
				DdeStopOrderColumns.Id,
				DdeStopOrderColumns.TypeCode,
				DdeStopOrderColumns.SecurityCode,
				DdeStopOrderColumns.SecurityClass,
				DdeStopOrderColumns.Price,
				DdeStopOrderColumns.Volume,
				DdeStopOrderColumns.Balance,
				DdeStopOrderColumns.Direction,
				DdeStopOrderColumns.Time,
				DdeStopOrderColumns.State,
				DdeStopOrderColumns.Account,
				DdeStopOrderColumns.DerivedOrderId,
				DdeStopOrderColumns.StopPrice,
				DdeStopOrderColumns.OtherSecurityCode,
				DdeStopOrderColumns.OtherSecurityClass,
				DdeStopOrderColumns.StopPriceCondition,
				DdeStopOrderColumns.ExpiryDate,
				DdeStopOrderColumns.LinkedOrderId,
				DdeStopOrderColumns.LinkedOrderPrice,
				DdeStopOrderColumns.OffsetValue,
				DdeStopOrderColumns.OffsetType,
				DdeStopOrderColumns.SpreadValue,
				DdeStopOrderColumns.SpreadType,
				DdeStopOrderColumns.ActiveTime,
				DdeStopOrderColumns.ActiveFrom,
				DdeStopOrderColumns.ActiveTo,
				DdeStopOrderColumns.CancelTime,
				DdeStopOrderColumns.Comment,
				DdeStopOrderColumns.StopLimitMarket,
				DdeStopOrderColumns.StopLimitPrice,
				DdeStopOrderColumns.StopLimitCondition,
				DdeStopOrderColumns.TakeProfitMarket,
				DdeStopOrderColumns.ConditionOrderId,
				DdeStopOrderColumns.Type,
				DdeStopOrderColumns.TransactionId,
				DdeStopOrderColumns.Date,
				DdeStopOrderColumns.ClientCode,
				DdeStopOrderColumns.Result
			});

			MyTradesTable = new DdeTable(DdeTableTypes.MyTrade, "мои сделки", new[]
			{
				DdeMyTradeColumns.Id,
				DdeMyTradeColumns.Time,
				DdeMyTradeColumns.SecurityCode,
				DdeMyTradeColumns.SecurityClass,
				DdeMyTradeColumns.Price,
				DdeMyTradeColumns.Volume,
				DdeMyTradeColumns.OrderId,
				DdeMyTradeColumns.Date
			});

			QuotesTable = new DdeTable(DdeTableTypes.Quote, "стакан", new[]
			{
				DdeQuoteColumns.AskVolume,
				DdeQuoteColumns.Price,
				DdeQuoteColumns.BidVolume
			});

			EquityPortfoliosTable = new DdeTable(DdeTableTypes.EquityPortfolio, "портфель по бумагам", new[]
			{
			    DdeEquityPortfolioColumns.ClientCode,
				DdeEquityPortfolioColumns.BeginCurrency,
				DdeEquityPortfolioColumns.CurrentCurrency,
				DdeEquityPortfolioColumns.CurrentLeverage
			});

			DerivativePortfoliosTable = new DdeTable(DdeTableTypes.DerivativePortfolio, "портфель по деривативам", new[]
			{
			    DdeDerivativePortfolioColumns.Account,
				DdeDerivativePortfolioColumns.CurrentLimitPositionsPrice,
				DdeDerivativePortfolioColumns.CurrentLimitPositionsOrdersPrice,
				DdeDerivativePortfolioColumns.Margin,
				DdeDerivativePortfolioColumns.LimitType
			});

			EquityPositionsTable = new DdeTable(DdeTableTypes.EquityPosition, "позиции по бумагам", new[]
			{
				DdeEquityPositionColumns.ClientCode,
				DdeEquityPositionColumns.Account,
				DdeEquityPositionColumns.SecurityCode,
				DdeEquityPositionColumns.BeginPosition,
				DdeEquityPositionColumns.CurrentPosition,
				DdeEquityPositionColumns.BlockedPosition
			});

			DerivativePositionsTable = new DdeTable(DdeTableTypes.DerivativePosition, "позиции по деривативам", new[]
			{
				DdeDerivativePositionColumns.Account,
				DdeDerivativePositionColumns.SecurityCode,
				DdeDerivativePositionColumns.BeginPosition,
				DdeDerivativePositionColumns.CurrentPosition,
				DdeDerivativePositionColumns.CurrentBidsVolume,
				DdeDerivativePositionColumns.CurrentAsksVolume
			});

			CurrencyPortfoliosTable = new DdeTable(DdeTableTypes.CurrencyPortfolio, "валюты портфелей", new[]
			{
			    DdeCurrencyPortfolioColumns.ClientCode,
				DdeCurrencyPortfolioColumns.FirmId,
				DdeCurrencyPortfolioColumns.Currency
			});
		}

		/// <summary>
		/// Асинхронный режим. Если true, то все транзакции, такие как <see cref="ITrader.RegisterOrder"/>
		/// или <see cref="ITrader.CancelOrder"/> будут отправляться в асинхронном режиме.
		/// </summary>
		/// <remarks>
		/// Значение по умолчанию true.
		/// </remarks>
		public bool IsAsyncMode { get; set; }

		/// <summary>
		/// Обработать поступающие DDE данные (событие вызывается до всех остальных событий <see cref="QuikTrader"/>).
		/// </summary>
		public event Action<string, IList<IList<object>>> PreProcessDdeData;

		/// <summary>
		/// Обработать неизвестные DDE данные.
		/// </summary>
		public event Action<string, IList<IList<object>>> ProcessUnknownDdeData;

		/// <summary>
		/// Обработать известные DDE данные.
		/// </summary>
		public event Action<string, IDictionary<object, IList<object>>> ProcessWellKnownDdeData;

		/// <summary>
		/// Обработать новые строчки таблицы, зарегистрированной через <see cref="CustomTables"/>.
		/// </summary>
		public event Action<Type, IEnumerable<object>> NewCustomTables
		{
			add { CustomTableDeserializer.NewCustomTables += value; }
			remove { CustomTableDeserializer.NewCustomTables -= value; }
		}

		/// <summary>
		/// Обработать изменения строчек таблицы, зарегистрированной через <see cref="CustomTables"/>.
		/// </summary>
		public event Action<Type, IEnumerable<object>> CustomTablesChanged
		{
			add { CustomTableDeserializer.CustomTablesChanged += value; }
			remove { CustomTableDeserializer.CustomTablesChanged -= value; }
		}

		/// <summary>
		/// Обработать новые строчки таблицы Инструменты (изменения).
		/// </summary>
		public event Action<IEnumerable<SecurityChange>> NewSecurityChanges;

		/// <summary>
		/// Отформатировать транзакцию (добавить, удалить или заменить параметры) перед тем, как она будет отправлена в Quik.
		/// </summary>
		public event Action<Transaction> FormatTransaction;

		/// <summary>
		/// Проверить соединение.
		/// </summary>
		public override bool IsConnected
		{
			get
			{
				try
				{
					return Api.IsConnected;
				}
				catch (Exception ex)
				{
					// mika Приводит к тому, что если в обработчике проверять IsConnected у шлюза,
					// то опять возникнет это исключение и так до бесконечности
					//RaiseConnectionError(ex);

					this.AddErrorLog(ex);

					return false;
				}
			}
		}

		/// <summary>
		/// Запущен ли экспорт. Экспорт запускается через метод <see cref="BaseTrader.StartExport"/>.
		/// </summary>
		public override bool IsExportStarted
		{
			get
			{
				var terminal = Terminal;
				return terminal != null && terminal.IsExportStarted;
			}
		}

		/// <summary>
		/// Настройки DDE таблицы Инструменты.
		/// </summary>
		public DdeTable SecuritiesTable { get; private set; }

		/// <summary>
		/// Настройки DDE таблицы Инструменты (изменения).
		/// </summary>
		public DdeTable SecuritiesChangeTable { get; private set; }

		/// <summary>
		/// Настройки DDE таблицы Сделки.
		/// </summary>
		public DdeTable TradesTable { get; private set; }

		/// <summary>
		/// Настройки DDE таблицы Мои Сделки.
		/// </summary>
		public DdeTable MyTradesTable { get; private set; }

		/// <summary>
		/// Настройки DDE таблицы Заявки.
		/// </summary>
		public DdeTable OrdersTable { get; private set; }

		/// <summary>
		/// Настройки DDE таблицы Стоп-Заявки.
		/// </summary>
		public DdeTable StopOrdersTable { get; private set; }

		/// <summary>
		/// Настройки DDE таблицы со стаканом.
		/// </summary>
		public DdeTable QuotesTable { get; private set; }

		/// <summary>
		/// Настройки DDE таблицы Портфель по бумагам.
		/// </summary>
		public DdeTable EquityPortfoliosTable { get; private set; }

		/// <summary>
		/// Настройки DDE таблицы Портфель по деривативам.
		/// </summary>
		public DdeTable DerivativePortfoliosTable { get; private set; }

		/// <summary>
		/// Настройки DDE таблицы Позиции по бумагам.
		/// </summary>
		public DdeTable EquityPositionsTable { get; private set; }

		/// <summary>
		/// Настройки DDE таблицы Позиции по деривативам.
		/// </summary>
		public DdeTable DerivativePositionsTable { get; private set; }

		/// <summary>
		/// Настройки DDE таблицы Валюты портфелей.
		/// </summary>
		public DdeTable CurrencyPortfoliosTable { get; private set; }

		/// <summary>
		/// Использовать в экспорте таблицу <see cref="CurrencyPortfoliosTable"/>. По-умолчанию не используется.
		/// </summary>
		public bool UseCurrencyPortfolios { get; set; }

		/// <summary>
		/// Использовать в экспорте таблицу <see cref="SecuritiesChangeTable"/>. По-умолчанию не используется.
		/// </summary>
		public bool UseSecuritiesChange { get; set; }

		/// <summary>
		/// Список произвольных таблиц.
		/// </summary>
		public IList<DdeCustomTable> CustomTables
		{
			get { return CustomTableDeserializer.CustomTables; }
		}

		internal DdeCustomTableDeserializer CustomTableDeserializer { get; private set; }

		/// <summary>
		/// Отменить заявку (перевести <see cref="Order.State"/> в <see cref="OrderStates.Done"/>) мгновенно после получения подтверждения от биржи.
		/// </summary>
		/// <remarks>
		/// Значение по умолчанию false. Подтверждения состояния заявки ожидается через DDE.
		/// </remarks>
		public bool CancelOrderInstantly { get; set; }

		/// <summary>
		/// Загружать заявки, поданные вручную через Quik.
		/// </summary>
		/// <remarks>
		/// Значение по умолчанию false.
		/// </remarks>
		public bool SupportManualOrders { get; set; }

		/// <summary>
		/// Соответствия кодов инструмента РТС-Стандарт из таблицы "Позиции по деривативам" кодам из таблицы "Инструменты".
		/// </summary>
		/// <remarks>
		/// Код из таблицы "Позиции по деривативам" указывается без последних двух цифр.
		/// </remarks>
		public Dictionary<string, string> RtsStandardDerivativesToSecurities = new Dictionary<string, string>
		{
			{"AFLT", "AFLT"},
			{"CHMF", "CHMF"},
			{"FEES", "FEES"},
			{"GAZR", "GAZP"},
			{"GMKR", "GMKN"},
			{"HYDR", "HYDR"},
			{"IRAO", "IRAO"},
			{"LKOH", "LKOH"},
			{"MAGN", "MAGN"},
			{"MRKH", "MRKH"},
			{"MSNG", "MSNG"},
			{"MTSI", "MTSS"},
			{"NOTK", "NVTK"},
			{"NLMK", "NLMK"},
			{"NMTP", "NMTP"},
			{"OGKA", "OGKA"},
			{"OGKB", "OGKB"},
			{"OGKC", "OGKC"},
			{"PMTL", "PMTL"},
			{"RASP", "RASP"},
			{"ROSN", "ROSN"},
			{"RSTR", "RSTR"},
			{"RTKM", "RTKM"},
			{"SBRF", "SBER"},
			{"SBPR", "SBERP"},
			{"SIBN", "SIBN"},
			{"SNGR", "SNGS"},
			{"SNGP", "SNGSP"},
			{"TATN", "TATN"},
			{"TRNF", "TRNFP"},
			{"TGKA", "TGKA"},
			{"URKA", "URKA"},
			{"VTBR", "VTBR"}
		};

		/// <summary>
		/// Получить соответствие кода инструмента РТС-стандарт из таблицы "Позиции по деривативам" коду из таблицы "Инструменты".
		/// </summary>
		/// <param name="derivativeCode">Имя инструмента из таблицы "Позиции по деривативам".</param>
		/// <param name="isRtsStandard">Является ли инструмент известным инструментом с рынка РТС-Стандарт.</param>
		/// <returns>Код инструмента из таблицы "Инструменты".</returns>
		private string GetSecurityCode(string derivativeCode, out bool isRtsStandard)
		{
			string securityName = null;

			if (derivativeCode.Length >= 3)
			{
				// Все РТС-Стандарт инструменты оканчиваются на <буква><цифра><цифра>.
				// берем последние три символа и проверям (с конца для скорости) на паттерн <буква><цифра><цифра>.
				// этим мы отсекаем фьючерсы, которые могут иметь вид <.><цифра><цифра> или <буква><буква><цифра>
				var suffix = derivativeCode.Substring(derivativeCode.Length - 3);
				if (Char.IsNumber(suffix[2]) && Char.IsNumber(suffix[1]) && Char.IsLetter(suffix[0]))
				{
					// вырезаем последние две цифры и ищем код в таблице
					var derivativeName = derivativeCode.Substring(0, derivativeCode.Length - 2);
					securityName = RtsStandardDerivativesToSecurities.TryGetValue(derivativeName);
				}
			}

			//Если не нашли в словаре - возвращаем исходное имя
			isRtsStandard = securityName != null;
			return isRtsStandard ? securityName : derivativeCode;
		}

		/// <summary>
		/// Подключиться к торговой системе.
		/// </summary>
		protected override void OnConnect()
		{
			var terminal = GetTerminal();

			if (!terminal.IsLaunched)
				terminal.AssignProcess();

			Api.Connect(terminal.DirectoryName);
		}

		/// <summary>
		/// Отключиться от торговой системы.
		/// </summary>
		protected override void OnDisconnect()
		{
			Api.Disconnect();
		}

		private void Async(Action action)
		{
			action.DoAsync(RaiseProcessDataError);
		}

		private void OnConnectionChanged(Codes code, Exception error, string message)
		{
			Async(() =>
			{
				if (error == null)
				{
					switch (code)
					{
						case Codes.DllConnected:
						case Codes.QuikConnected:
							Debug.WriteLine(IsConnected);

							try
							{
								GetTerminal().AssignProcess();

								//_lastSyncTime = default(DateTime);

								if (IsConnected)
									RaiseConnected();
								else // http://groups.google.ru/group/stocksharp/msg/4112b3dcc2f11b76
									RaiseConnectionError(new ApiException(code, "Терминал не подключен к серверу."));
							}
							catch (Exception ex)
							{
								RaiseConnectionError(ex);
							}
							break;
						case Codes.DllDisconnected:
							RaiseDisconnected();
							break;
						case Codes.QuikDisconnected:
							RaiseConnectionError(new ApiException(code, message));
							break;
						default:
							RaiseConnectionError(new InvalidOperationException("Неизвестное состояние соединения '{0}'.".Put(code)));
							break;
					}
				}
				else
				{
					RaiseConnectionError(error);
				}
			});
		}

		private string CreateSecurityId(Func<DdeTableColumn, object> func, DdeTableColumn codeColumn, DdeTableColumn classColumn)
		{
			return CreateSecurityId(func.Get<string>(codeColumn), func.Get<string>(classColumn));
		}

		private static void AddWellKnownDdeData<TItem, TId>(Dictionary<object, IList<object>> wellKnownDdeData, TItem item, IList<object> row, TId id)
			where TItem : class
		{
			if (wellKnownDdeData == null)
				throw new ArgumentNullException("wellKnownDdeData");

			if (item == null)
				throw new ArgumentNullException("item");

			if (row == null)
				throw new ArgumentNullException("row");

			if (wellKnownDdeData.ContainsKey(item))
				throw new ArgumentException("{0} с номером '{1}' дублируется в полученном DDE пакете.".Put(typeof(TItem).Name, id), "item");
			else
				wellKnownDdeData.Add(item, row);
		}

		private void OnPoke(string category, IList<IList<object>> rows)
		{
			var isWellKnown = true;
			var wellKnownDdeData = new Dictionary<object, IList<object>>();
			var categoryLow = category.ToLowerInvariant();

			ProcessEvents(() =>
			{
				try
				{
					PreProcessDdeData.SafeInvoke(category, rows);
				}
				catch (Exception ex)
				{
					RaiseProcessDataError(ex);
				}

				if (categoryLow.CompareIgnoreCase(MyTradesTable.Caption))
				{
					MyTradesTable.Deserialize(rows, (row, func) =>
					{
						if (IsEveningClass(func, DdeMyTradeColumns.SecurityClass))
							return;

						ProcessSecurityAction(CreateSecurityId(func, DdeMyTradeColumns.SecurityCode, DdeMyTradeColumns.SecurityClass), null, security =>
						{
							var tradeId = func.Get<long>(DdeMyTradeColumns.Id);
							var orderId = func.Get<long>(DdeMyTradeColumns.OrderId);

							AddMyTrade(security, orderId, 0, tradeId,
								id =>
								{
									var t = EntityFactory.CreateTrade(security, id);

									t.Time = func.GetNullable(DdeMyTradeColumns.Date, DateTime.MinValue) + func.GetNullable<TimeSpan>(DdeMyTradeColumns.Time);
									t.Price = func.Get<decimal>(DdeMyTradeColumns.Price);
									t.Volume = func.Get<decimal>(DdeMyTradeColumns.Volume);

									t.InitLatency();

									return t;
								},
								trade =>
								{
									ExportExtendedProperties(MyTradesTable, trade, row, func);
									AddWellKnownDdeData(wellKnownDdeData, trade, row, trade.Trade.Id);
								});
						});
					}, RaiseProcessDataError, true);
				}
				else if (categoryLow.CompareIgnoreCase(TradesTable.Caption))
				{
					var hasDirection = TradesTable.Columns.Contains(DdeTradeColumns.OrderDirection);

					TradesTable.Deserialize(rows, (row, func) =>
					{
						if (IsEveningClass(func, DdeTradeColumns.SecurityClass))
							return;

						ProcessSecurityAction(CreateSecurityId(func, DdeTradeColumns.SecurityCode, DdeTradeColumns.SecurityClass), null, security =>
						{
							var id = func.Get<long>(DdeTradeColumns.Id);

							var lastTrade = GetTrade(security, id, key =>
							{
								var trade = EntityFactory.CreateTrade(security, id);

								trade.Time = func.GetNullable(DdeTradeColumns.Date, DateTime.MinValue) + func.GetNullable<TimeSpan>(DdeTradeColumns.Time);
								trade.Price = func.Get<decimal>(DdeTradeColumns.Price);
								trade.Volume = func.Get<decimal>(DdeTradeColumns.Volume);

								trade.InitLatency();

								if (hasDirection)
								{
									var value = func(DdeTradeColumns.OrderDirection);

									if (value is string && (string)value != string.Empty)
										trade.OrderDirection = ((string)value).Substring(0, 1).ToDirection();
								}

								ExportExtendedProperties(TradesTable, trade, row, func);
								AddWellKnownDdeData(wellKnownDdeData, trade, row, trade.Id);

								return trade;
							});

							// http://stocksharp.com/forum/yaf_postsm6116_Ratspriedlozhieniie-po-tablitsie-Instrumienty.aspx#post6116
							//
							var columns = SecuritiesTable.Columns;

							if (!(columns.Contains(DdeSecurityColumns.LastTradeTime) ||
								columns.Contains(DdeSecurityColumns.LastChangeTime) ||
								columns.Contains(DdeSecurityColumns.LastTradeVolume) ||
								columns.Contains(DdeSecurityColumns.LastTradeVolume2) ||
								columns.Contains(DdeSecurityColumns.LastTradePrice)))
							{
								using (security.BeginUpdate())
								{
									security.LastTrade = lastTrade;
								}

								RaiseSecurityChanged(security);
							}
						});
					}, RaiseProcessDataError, true);
				}
				else if (categoryLow.CompareIgnoreCase(SecuritiesTable.Caption))
				{
					SecuritiesTable.Deserialize(rows, (row, func) =>
					{
						if (IsEveningClass(func, DdeSecurityColumns.Class))
							return;

						var code = func.Get<string>(DdeSecurityColumns.Code);
						var secClass = func.Get<string>(DdeSecurityColumns.Class);

						GetSecurity(CreateSecurityId(code, secClass), security =>
						{
							using (security.BeginUpdate())
							{
								var columns = SecuritiesTable.Columns;

								security.Name = func.Get<string>(DdeSecurityColumns.Name);

								if (columns.Contains(DdeSecurityColumns.ShortName))
									security.ShortName = func.Get<string>(DdeSecurityColumns.ShortName);

								security.Code = code;
								security.Class = secClass;
								security.MinLotSize = func.GetNullable<int>(DdeSecurityColumns.MinLotSize);
								security.MinStepSize = func.GetNullable<decimal>(DdeSecurityColumns.MinStepSize);

								var info = GetSecurityClassInfo(security.Class);

								security.Type = security.MinLotSize == 0 ? SecurityTypes.Index : info.First;
								security.ExchangeBoard = info.Second;
								security.MinStepPrice = columns.Contains(DdeSecurityColumns.MinStepPrice)
									? func.GetNullable<decimal>(DdeSecurityColumns.MinStepPrice, 1)
									: security.MinStepSize;

								if (columns.Contains(DdeSecurityColumns.MinPrice))
									security.MinPrice = func.GetNullable<decimal>(DdeSecurityColumns.MinPrice);

								if (columns.Contains(DdeSecurityColumns.MaxPrice))
									security.MaxPrice = func.GetNullable<decimal>(DdeSecurityColumns.MaxPrice);

								if (columns.Contains(DdeSecurityColumns.SettlementDate))
									security.SettlementDate = func.GetNullable2<DateTime>(DdeSecurityColumns.SettlementDate);

								if (columns.Contains(DdeSecurityColumns.ExpiryDate))
									security.ExpiryDate = func.GetNullable2<DateTime>(DdeSecurityColumns.ExpiryDate);

								if (columns.Contains(DdeSecurityColumns.ImpliedVolatility))
									security.ImpliedVolatility = func.GetNullable<decimal>(DdeSecurityColumns.ImpliedVolatility);

								if (columns.Contains(DdeSecurityColumns.TheorPrice))
									security.TheorPrice = func.GetNullable<decimal>(DdeSecurityColumns.TheorPrice);

								if (columns.Contains(DdeSecurityColumns.Strike))
									security.Strike = func.GetNullable<decimal>(DdeSecurityColumns.Strike);

								if (columns.Contains(DdeSecurityColumns.UnderlyingSecurity))
									security.UnderlyingSecurityId = CreateSecurityId(func.Get<string>(DdeSecurityColumns.UnderlyingSecurity), secClass);

								if (columns.Contains(DdeSecurityColumns.OptionType))
									security.OptionType = func.GetOptionType();

								if (security.Type == SecurityTypes.Index && columns.Contains(DdeSecurityColumns.IndexCurrentPrice))
								{
									security.LastTrade = EntityFactory.CreateTrade(security, 0);
									security.LastTrade.Price = func.GetNullable<decimal>(DdeSecurityColumns.IndexCurrentPrice);
								}

								security.State = func.GetSecurityState();

								var bid = columns.BuildBid(security, func);
								if (bid != null)
									security.BestBid = bid;

								var ask = columns.BuildAsk(security, func);
								if (ask != null)
									security.BestAsk = ask;

								var lastTrade = columns.BuildTrade(() => EntityFactory.CreateTrade(security, 0), func);

								if (lastTrade != null)
									security.LastTrade = lastTrade;
								else
									security.LastChangeTime = security.GetMarketTime();

								if (columns.Contains(DdeSecurityColumns.OpenPrice))
									security.OpenPrice = func.GetNullable<decimal>(DdeSecurityColumns.OpenPrice);

								if (columns.Contains(DdeSecurityColumns.ClosePrice))
									security.ClosePrice = func.GetNullable<decimal>(DdeSecurityColumns.ClosePrice);

								if (columns.Contains(DdeSecurityColumns.LowPrice))
									security.LowPrice = func.GetNullable<decimal>(DdeSecurityColumns.LowPrice);

								if (columns.Contains(DdeSecurityColumns.HighPrice))
									security.HighPrice = func.GetNullable<decimal>(DdeSecurityColumns.HighPrice);

								if (columns.Contains(DdeSecurityColumns.MarginBuy))
									security.MarginBuy = func.GetNullable<decimal>(DdeSecurityColumns.MarginBuy);

								if (columns.Contains(DdeSecurityColumns.MarginSell))
									security.MarginSell = func.GetNullable<decimal>(DdeSecurityColumns.MarginSell);

								if (columns.Contains(DdeSecurityColumns.OpenPositions))
									security.OpenInterest = func.GetNullable<decimal>(DdeSecurityColumns.OpenPositions);

								if (columns.Contains(DdeSecurityColumns.NominalCurrency))
									security.Currency = func.Get<string>(DdeSecurityColumns.NominalCurrency).ToCurrencyType();

								if (columns.Contains(DdeSecurityColumns.BidsCount))
									security.BidsCount = func.GetNullable<decimal>(DdeSecurityColumns.BidsCount);

								if (columns.Contains(DdeSecurityColumns.BidsVolume))
									security.BidsVolume = func.GetNullable<decimal>(DdeSecurityColumns.BidsVolume);

								if (columns.Contains(DdeSecurityColumns.AsksCount))
									security.AsksCount = func.GetNullable<decimal>(DdeSecurityColumns.AsksCount);

								if (columns.Contains(DdeSecurityColumns.AsksVolume))
									security.AsksVolume = func.GetNullable<decimal>(DdeSecurityColumns.AsksVolume);

								if (columns.Contains(DdeSecurityColumns.ISIN))
									security.ExternalId.Isin = func.Get<string>(DdeSecurityColumns.ISIN);

								ExportExtendedProperties(SecuritiesTable, security, row, func);
								AddWellKnownDdeData(wellKnownDdeData, security, row, security.Id);
							}
							return true;
						});
					}, RaiseProcessDataError, true);
				}
				else if (categoryLow.CompareIgnoreCase(OrdersTable.Caption))
				{
					OrdersTable.Deserialize(rows, (row, func) =>
					{
						if (IsEveningClass(func, DdeOrderColumns.SecurityClass))
							return;

						Order order = null;

						var transactionId = func.Get<long>(DdeOrderColumns.TransactionId);

						if (transactionId != 0)
							order = GetOrderByTransactionId(transactionId);
						else if (!SupportManualOrders)
							return;

						if (order == null || order.Type == OrderTypes.Conditional)
						{
							ProcessSecurityAction(CreateSecurityId(func, DdeOrderColumns.SecurityCode, DdeOrderColumns.SecurityClass), null, security =>
							{
								var stopOrder = order;
								var id = func.Get<long>(DdeOrderColumns.Id);

								order = GetOrder(security, id, key =>
								{
									var o = EntityFactory.CreateOrder(security, key);

									o.Price = func.Get<decimal>(DdeOrderColumns.Price);
									o.Volume = func.Get<decimal>(DdeOrderColumns.Volume);
									o.Direction = func(DdeOrderColumns.Direction).ToDirection();
									o.Portfolio = GetPortfolio(func.Get<string>(DdeOrderColumns.Account), func.Get<string>(DdeOrderColumns.ClientCode));
									o.Type = func.GetOrderType();
									o.ExecutionCondition = func.GetExecutionCondition();
									o.Comment = func.Get<string>(DdeOrderColumns.Comment);
									o.TransactionId = transactionId;
									o.State = OrderStates.Active;
									o.ExpiryDate = func.GetExpiryDate(DdeOrderColumns.ExpiryDate);

									return o;
								}, o => UpdateOrder(func, o, row));

								//TODO: esper временное решение, необходимость данного кода под вопросом.
								if (stopOrder != null)
								{
									var changeStopOrder = false;

									using (stopOrder.BeginUpdate())
									{
										if (stopOrder.DerivedOrder == null)
										{
											stopOrder.DerivedOrder = order;
											changeStopOrder = true;
										}

										if (stopOrder.State == OrderStates.Active)
										{
											stopOrder.State = OrderStates.Done;
											changeStopOrder = true;
										}
									}

									if (changeStopOrder)
										RaiseOrderChanged(stopOrder);
								}
							});
						}
						else
						{
							GetOrder(order.Security, func.Get<long>(DdeOrderColumns.Id), key =>
							{
								using (order.BeginUpdate())
								{
									order.Id = key;
									order.State = OrderStates.Active;
									order.Volume = func.Get<decimal>(DdeOrderColumns.Volume);
								}
								return order;
							}, o => UpdateOrder(func, o, row));
						}

						if (order != null)
							AddWellKnownDdeData(wellKnownDdeData, order, row, order.Id);
					}, RaiseProcessDataError, true);
				}
				else if (categoryLow.CompareIgnoreCase(StopOrdersTable.Caption))
				{
					StopOrdersTable.Deserialize(rows, (row, func) =>
					{
						if (IsEveningClass(func, DdeStopOrderColumns.SecurityClass))
							return;

						Order order = null;

						var transactionId = func.Get<long>(DdeStopOrderColumns.TransactionId);

						if (transactionId != 0)
							order = GetStopOrderByTransactionId(transactionId);

						if (order == null)
						{
							ProcessSecurityAction(CreateSecurityId(func, DdeStopOrderColumns.SecurityCode, DdeStopOrderColumns.SecurityClass), null, security =>
							{
								var otherSecurityCode = func.Get<string>(DdeStopOrderColumns.OtherSecurityCode);

								Action<Security> otherSecHandler = otherSec =>
								{
									var id = func.Get<long>(DdeStopOrderColumns.Id);

									order = GetOrder(security, id, key =>
									{
										var isActiveTime = func.GetBool(DdeStopOrderColumns.ActiveTime);
										var date = func.Get<DateTime>(DdeStopOrderColumns.Date);

										var condition = new QuikOrderCondition
										{
											StopPrice = func.Get<decimal>(DdeStopOrderColumns.StopPrice),
											StopPriceCondition = func.GetStopPriceCondition(),
											OtherSecurity = otherSec,
											LinkedOrderPrice = func.GetZeroable<decimal>(DdeStopOrderColumns.LinkedOrderPrice),
											Offset = func.GetUnit(DdeStopOrderColumns.OffsetType, DdeStopOrderColumns.OffsetValue),
											Spread = func.GetUnit(DdeStopOrderColumns.SpreadType, DdeStopOrderColumns.SpreadValue),
											ActiveTime = isActiveTime == null ? null : new Range<DateTime>(date + func(DdeStopOrderColumns.ActiveFrom).To<TimeSpan>(), date + func(DdeStopOrderColumns.ActiveTo).To<TimeSpan>()),
											IsMarketStopLimit = func.GetBool(DdeStopOrderColumns.StopLimitMarket),
											StopLimitPrice = func.GetNullable2<decimal>(DdeStopOrderColumns.StopLimitPrice),
											IsMarketTakeProfit = func.GetBool(DdeStopOrderColumns.TakeProfitMarket),
											Type = func.GetStopOrderType(),
											Result = func.GetStopResult(),
										};

										var o = EntityFactory.CreateOrder(security, id);

										o.Portfolio = GetPortfolio(func.Get<string>(DdeStopOrderColumns.Account), func.Get<string>(DdeStopOrderColumns.ClientCode));
										o.Price = func.Get<decimal>(DdeStopOrderColumns.Price);
										o.Volume = func.Get<decimal>(DdeStopOrderColumns.Volume);
										o.Direction = func(DdeStopOrderColumns.Direction).ToDirection();
										o.Type = OrderTypes.Conditional;
										o.Comment = func.Get<string>(DdeStopOrderColumns.Comment);
										o.ExpiryDate = func.GetExpiryDate(DdeStopOrderColumns.ExpiryDate);
										o.TransactionId = transactionId;
										o.Condition = condition;
										o.State = OrderStates.Active;
										o.ExtensionInfo = new Dictionary<object, object>();

										// http://www.quik.ru/forum/ideas/51556/
										if (condition.Type == QuikOrderConditionTypes.OtherSecurity && condition.OtherSecurity == null)
											condition.OtherSecurity = o.Security;

										var code = func.Get<string>(DdeStopOrderColumns.TypeCode);

										var conditionOrderId = func.GetZeroable<long>(DdeStopOrderColumns.ConditionOrderId);
										if (conditionOrderId != null)
										{
											AddDerivedOrder(security, (long)conditionOrderId, o, (s, o1) => condition.ConditionOrder = o1);
											condition.ConditionOrderPartiallyMatched = code.Contains('P');
											condition.ConditionOrderUseMatchedBalance = code.Contains('U');
										}
										else if (condition.LinkedOrderPrice != null)
										{
											condition.LinkedOrderCancel = code.Contains('P');
										}

										return o;
									}, o => UpdateStopOrder(func, o, row));
								};

								if (otherSecurityCode.IsEmpty())
									otherSecHandler(null);
								else
									ProcessSecurityAction(CreateSecurityId(otherSecurityCode, func.Get<string>(DdeStopOrderColumns.OtherSecurityClass)), null, otherSecHandler);
							});
						}
						else
						{
							GetOrder(order.Security, func.Get<long>(DdeStopOrderColumns.Id), key =>
							{
								using (order.BeginUpdate())
								{
									order.Id = key;
									order.State = OrderStates.Active;
								}
								return order;
							}, o => UpdateStopOrder(func, o, row));
						}

						if (order != null)
							AddWellKnownDdeData(wellKnownDdeData, order, row, order.Id);
					}, RaiseProcessDataError, true);
				}
				else if (categoryLow.CompareIgnoreCase(EquityPortfoliosTable.Caption))
				{
					var table = EquityPortfoliosTable;

					table.Deserialize(rows, (row, func) =>
					{
						var clientCode = func.Get<string>(DdeEquityPortfolioColumns.ClientCode);

						GetPortfolio(clientCode, portfolio =>
						{
							using (portfolio.BeginUpdate())
							{
								portfolio.ExchangeBoard = ExchangeBoard.Micex;
								portfolio.BeginValue = func.Get<decimal>(DdeEquityPortfolioColumns.BeginCurrency);
								portfolio.CurrentValue = func.Get<decimal>(DdeEquityPortfolioColumns.CurrentCurrency);
								portfolio.Leverage = func.Get<decimal>(DdeEquityPortfolioColumns.CurrentLeverage);

								ExportExtendedProperties(table, portfolio, row, func);
								AddWellKnownDdeData(wellKnownDdeData, portfolio, row, portfolio.Name);

								return true;
							}
						});
					}, RaiseProcessDataError, true);
				}
				else if (categoryLow.CompareIgnoreCase(DerivativePortfoliosTable.Caption))
				{
					DerivativePortfoliosTable.Deserialize(rows, (row, func) =>
					{
						var type = func.GetLimitType();

						if (type == DerivativeLimitTypes.Money)
						{
							var account = func.Get<string>(DdeDerivativePortfolioColumns.Account);

							GetPortfolio(account, portfolio =>
							{
								using (portfolio.BeginUpdate())
								{
									portfolio.ExchangeBoard = ExchangeBoard.Forts;
									portfolio.BeginValue = func.Get<decimal>(DdeDerivativePortfolioColumns.CurrentLimitPositionsPrice);
									portfolio.CurrentValue = func.Get<decimal>(DdeDerivativePortfolioColumns.CurrentLimitPositionsOrdersPrice);
									portfolio.Leverage = 0;
									portfolio.VariationMargin = func.Get<decimal>(DdeDerivativePortfolioColumns.Margin);

									var columns = DerivativePortfoliosTable.Columns;
									if (columns.Contains(DdeDerivativePortfolioColumns.ACI))
										portfolio.VariationMargin += func.Get<decimal>(DdeDerivativePortfolioColumns.ACI);

									if (columns.Contains(DdeDerivativePortfolioColumns.MarketCommission))
										portfolio.Commission = func.Get<decimal>(DdeDerivativePortfolioColumns.MarketCommission);

									ExportExtendedProperties(DerivativePortfoliosTable, portfolio, row, func);
									AddWellKnownDdeData(wellKnownDdeData, portfolio, row, portfolio.Name);

									return true;
								}
							});
						}
					}, RaiseProcessDataError, true);
				}
				else if (categoryLow.CompareIgnoreCase(EquityPositionsTable.Caption))
				{
					EquityPositionsTable.Deserialize(rows, (row, func) =>
					{
						var secCode = func.Get<string>(DdeEquityPositionColumns.SecurityCode);

						var account = func.Get<string>(DdeEquityPositionColumns.Account);
						var clientCode = func.Get<string>(DdeEquityPositionColumns.ClientCode);

						// mika
						// вначале надо получить портфель, чтобы обновить в случае чего счет ДЕПО у него.
						// если получать сначала инструменты, то можно получить сообщение об ошибке, и счет ДЕПО не инициализируется
						var portfolio = GetPortfolio(account, clientCode);

						var security = Securities.FirstOrDefault(s => s.Code == secCode && s.Type == SecurityTypes.Stock);

						if (security == null)
							throw new InvalidOperationException("Инструмент с кодом {0} для бумажной позиции не найден.".Put(secCode));

						UpdatePosition(portfolio, security, account, position =>
						{
							using (position.BeginUpdate())
							{
								position.BeginValue = func.Get<long>(DdeEquityPositionColumns.BeginPosition);
								position.CurrentValue = func.Get<long>(DdeEquityPositionColumns.CurrentPosition);
								position.BlockedValue = func.Get<long>(DdeEquityPositionColumns.BlockedPosition);

								if (EquityPositionsTable.Columns.Contains(DdeEquityPositionColumns.BuyPrice))
									position.AveragePrice = func.Get<decimal>(DdeEquityPositionColumns.BuyPrice);

								ExportExtendedProperties(EquityPositionsTable, position, row, func);
								AddWellKnownDdeData(wellKnownDdeData, position, row, position.Security.Id + " " + position.Portfolio.Name);

								return true;
							}
						});
					}, RaiseProcessDataError, true);
				}
				else if (categoryLow.CompareIgnoreCase(DerivativePositionsTable.Caption))
				{
					DerivativePositionsTable.Deserialize(rows, (row, func) =>
					{
						var secCode = func.Get<string>(DdeDerivativePositionColumns.SecurityCode);

						// проверить не является ли это акцией РТС-Стандарт, если да то получить правильное название Security Code
						// Для примера secCode = GAZP01 => newSecCode = GAZP
						bool isRtsStandard;
						var newSecCode = GetSecurityCode(secCode, out isRtsStandard);

						var security = Securities.FirstOrDefault(s => s.Code == newSecCode &&
							(s.Type != SecurityTypes.Stock && s.Type != SecurityTypes.Index ||
							// http://groups.google.ru/group/stocksharp/msg/62fa5949275c3487
							(s.Type == SecurityTypes.Stock && s.Class == "RTSST")));

						if (security == null)
							throw new InvalidOperationException("Инструмент с кодом {0} для деривативной позиции не найден.".Put(secCode));

						var account = func.Get<string>(DdeDerivativePositionColumns.Account);

						var portfolio = GetPortfolio(account);

						// Если РТС-Стандарт, то в ShortName записываем имя РТС-Стандарт инструмента (GAZR01)
						// Тогда как сам инструмент будет GAZP
						if (isRtsStandard)
						{
							var rtsstSecurity = _rtsstSecurities.TryGetValue(secCode);
							if (rtsstSecurity == null)
							{
								rtsstSecurity = security.Clone();

								rtsstSecurity.ShortName = secCode;
								_rtsstSecurities.Add(secCode, rtsstSecurity);
							}

							security = rtsstSecurity;
						}

						UpdatePosition(portfolio, security, position =>
						{
							using (position.BeginUpdate())
							{
								position.BeginValue = func.Get<long>(DdeDerivativePositionColumns.BeginPosition);
								position.CurrentValue = func.Get<long>(DdeDerivativePositionColumns.CurrentPosition);
								position.BlockedValue = func.Get<long>(DdeDerivativePositionColumns.CurrentBidsVolume) + func.Get<long>(DdeDerivativePositionColumns.CurrentAsksVolume);

								ExportExtendedProperties(DerivativePositionsTable, position, row, func);
								AddWellKnownDdeData(wellKnownDdeData, position, row, position.Security.Id + " " + position.Portfolio.Name);

								return true;
							}
						});
					}, RaiseProcessDataError, true);
				}
				else if (categoryLow.CompareIgnoreCase(CurrencyPortfoliosTable.Caption))
				{
					CurrencyPortfoliosTable.Deserialize(rows, (row, func) =>
						UpdatePortfolio(func.Get<string>(DdeCurrencyPortfolioColumns.FirmId), func.Get<string>(DdeCurrencyPortfolioColumns.ClientCode),
							portfolio =>
							{
								portfolio.Currency = func.Get<string>(DdeCurrencyPortfolioColumns.Currency).ToCurrencyType();
								return true;
							}),
						RaiseProcessDataError, true);
				}
				else if (categoryLow.ContainsIgnoreCase(QuotesTable.Caption))
				{
					var startIndex = category.IndexOf(']') + 1;
					var marketDepthTitle = category.Substring(startIndex, category.Length - startIndex);

					var parts = marketDepthTitle.Split(SecurityIdGenerator.Delimiter);

					if (parts.Length != 2)
						throw new InvalidOperationException("Название таблицы со стаканом оформлено неверно '{0}'.".Put(category));

					ProcessSecurityAction(CreateSecurityId(parts[0], parts[1]), null, security =>
					{
						var quotes = new List<Quote>();

						QuotesTable.Deserialize(rows, (row, func) =>
						{
							var askVolume = func.GetNullable2<decimal>(DdeQuoteColumns.AskVolume);
							var bidVolume = func.GetNullable2<decimal>(DdeQuoteColumns.BidVolume);

							// квик посылает для стаканов иногда пустые значения
							// http://www.quik.ru/forum/iwr/53421/53421/
							if (askVolume != null && bidVolume != null)
							{
								var quote = new Quote
								{
									Security = security,
									Price = func.Get<decimal>(DdeQuoteColumns.Price),
									Volume = askVolume.Value.Max(bidVolume.Value),
									OrderDirection = bidVolume == 0 ? OrderDirections.Sell : OrderDirections.Buy,
								};

								quotes.Add(quote);

								ExportExtendedProperties(QuotesTable, quote, row, func);
								wellKnownDdeData.Add(quote, row);
							}
						}, ex => { throw new InvalidOperationException("Ошибка построения стакана для {0}.".Put(security), ex); }, false);

						RaiseMarketDepthChanged(GetMarketDepth(security).Update(quotes, security.GetMarketTime()).InitLatency());
					}, true);
				}
				else if (categoryLow.CompareIgnoreCase(SecuritiesChangeTable.Caption))
				{
					SecuritiesChangeTable.Deserialize(rows, (row, func) =>
					{
						if (IsEveningClass(func, DdeSecurityColumns.Class) || SecuritiesChangeTable.Columns.NonMandatoryColumns.Count == 0)
							return;

						var time = func.GetNullable(DdeSecurityColumns.LastChangeTime, DateTime.MinValue);

						//в таблице изменений могут быть строки с пустой датой,
						//значения остальных столбцов так же могут содержать невалидные значения.
						if (time == DateTime.MinValue)
							return;

						ProcessSecurityAction(CreateSecurityId(func, DdeSecurityColumns.Code, DdeSecurityColumns.Class), null, security =>
						{
							var changes = GetSecurityChanges(security, time, SecuritiesChangeTable.Columns.NonMandatoryColumns, func);

							if (!changes.IsEmpty())
								NewSecurityChanges.SafeInvoke(changes);
						});
					},
					RaiseProcessDataError, true);
				}
				else
				{
					isWellKnown = false;
				}
			});

			if (isWellKnown)
			{
				if (wellKnownDdeData.Count > 0)
					ProcessWellKnownDdeData.SafeInvoke(category, wellKnownDdeData);
			}
			else
			{
				if (!CustomTableDeserializer.TryDeserialize(categoryLow, rows))
				{
					if (ProcessUnknownDdeData != null)
						ProcessUnknownDdeData(category, rows);
					else
						RaiseProcessDataError(new ArgumentOutOfRangeException("category", category));
				}
			}
		}

		private IEnumerable<SecurityChange> GetSecurityChanges(Security security, DateTime time, IEnumerable<DdeTableColumn> columns, Func<DdeTableColumn, object> func)
		{
			var values = _securityChanges.SafeAdd(security);
			var changes = new List<SecurityChange>();

			var ask = columns.BuildAsk(security, func);
			if (ask != null && !ask.Equals(values.TryGetValue(DdeSecurityColumns.BestAskPrice)))
			{
				values[DdeSecurityColumns.BestAskPrice] = ask;
				changes.Add(new SecurityChange(security, time, SecurityChangeTypes.BestAsk, ask));
			}

			var bid = columns.BuildBid(security, func);
			if (bid != null && !bid.Equals(values.TryGetValue(DdeSecurityColumns.BestBidPrice)))
			{
				values[DdeSecurityColumns.BestBidPrice] = bid;
				changes.Add(new SecurityChange(security, time, SecurityChangeTypes.BestBid, bid));
			}

			foreach (var column in columns)
			{
				var converter = QuikDdeFormatter.DdeColumnValueToSecurityChangeConverters.TryGetValue(column);
				if (converter == null)
					continue;

				var value = func(column);
				var lastValue = values.TryGetValue(column);

				if (!value.Equals(lastValue))
				{
					values[column] = value;
					changes.Add(converter(security, time, value));
				}
			}

			return changes;
		}

		private bool IsEveningClass(Func<DdeTableColumn, object> func, DdeTableColumn classColumn)
		{
			var securityClass = func.Get<string>(classColumn);
			return _eveningClasses.Contains(securityClass);
		}

		private Portfolio GetPortfolio(string account, string clientCode)
		{
			return UpdatePortfolio(account, clientCode, pf => false);
		}

		private Portfolio UpdatePortfolio(string account, string clientCode, Func<Portfolio, bool> changePortfolio)
		{
			if (changePortfolio == null)
				throw new ArgumentNullException("changePortfolio");

			return GetPortfolio(clientCode, pf =>
			{
				var isChanged = false;

				if (!account.IsEmpty())
				{
					pf.ExtensionInfo[DdeEquityPositionColumns.Account] = account;
					isChanged = true;
				}

				return changePortfolio(pf) || isChanged;
			});
		}

		private bool UpdateStopOrder(Func<DdeTableColumn, object> func, Order order, IList<object> row)
		{
			if (func == null)
				throw new ArgumentNullException("func");

			if (order == null)
				throw new ArgumentNullException("order");

			if (row == null)
				throw new ArgumentNullException("row");

			using (order.BeginUpdate())
			{
				order.Time = func.Get<DateTime>(DdeStopOrderColumns.Date) + func.Get<TimeSpan>(DdeStopOrderColumns.Time);
				order.Balance = func.Get<decimal>(DdeStopOrderColumns.Balance);

				// Когда заявка уже была снята через асинхронный колбэк 
				// http://groups.google.ru/group/stocksharp/browse_thread/thread/c2a0adef8a430726#
				//
				if (order.State != OrderStates.Done)
					order.State = func.GetState(DdeStopOrderColumns.State) ?? OrderStates.Done;

				var cancelTime = func.GetNullable2<TimeSpan>(DdeStopOrderColumns.CancelTime);

				//order.CancelTime = cancelTime == null ? null : order.Time.Date + cancelTime;
				if (cancelTime != null)
					order.LastChangeTime = order.Time.Date + cancelTime.Value;
				else
					order.LastChangeTime = default(DateTime);

				var derivedOrderId = func.GetZeroable<long>(DdeStopOrderColumns.DerivedOrderId);
				if (derivedOrderId != null)
					AddDerivedOrder(order.Security, (long)derivedOrderId, order, (s, o) => s.DerivedOrder = o);

				var linkedOrderId = func.GetZeroable<long>(DdeStopOrderColumns.LinkedOrderId);
				if (linkedOrderId != null)
					AddDerivedOrder(order.Security, (long)linkedOrderId, order, (s, o) => ((QuikOrderCondition)s.Condition).LinkedOrder = o);

				CheckStopResult(order, func.GetStopResult());

				ExportExtendedProperties(StopOrdersTable, order, row, func);
			}

			return true;
		}

		private void CheckStopResult(Order order, QuikOrderConditionResults? result)
		{
			var quikCond = (QuikOrderCondition)order.Condition;
			quikCond.Result = result;

			if (result == QuikOrderConditionResults.RejectedByTS || result == QuikOrderConditionResults.LimitControlFailed)
			{
				var message = result == QuikOrderConditionResults.LimitControlFailed
					? "Недостаточно средств клиента для выполнения заявки, выставляемой по стоп-заявке {0}.".Put(order.Id)
					: "Заявка, выставляемая по стоп-заявке {0}, отвергнута торговой системой.".Put(order.Id);

				RaiseOrderFailed(order, new InvalidOperationException(message), false);
			}
		}

		private bool UpdateOrder(Func<DdeTableColumn, object> func, Order order, IList<object> row)
		{
			if (func == null)
				throw new ArgumentNullException("func");

			if (order == null)
				throw new ArgumentNullException("order");

			if (row == null)
				throw new ArgumentNullException("row");

			using (order.BeginUpdate())
			{
				order.Time = func.Get<DateTime>(DdeOrderColumns.Date) + func.Get<TimeSpan>(DdeOrderColumns.Time);
				order.Balance = func.Get<decimal>(DdeOrderColumns.Balance);

				var cancelTime = func.GetNullable2<TimeSpan>(DdeOrderColumns.CancelTime);
				//order.CancelTime = cancelTime == null ? null : order.Time.Date + cancelTime;
				if (null != cancelTime)
					order.LastChangeTime = order.Time.Date + cancelTime.Value;
				else
					order.LastChangeTime = default(DateTime);

				// Когда заявка уже была снята через асинхронный колбэк 
				// http://groups.google.ru/group/stocksharp/browse_thread/thread/c2a0adef8a430726#
				//
				if (order.State != OrderStates.Done)
				{
					if (order.Balance == 0)
						order.State = OrderStates.Done;
					else
					{
						var state = func.GetState(DdeOrderColumns.State);

						// заявка снята
						if (state == null)
						{
							// http://stocksharp.com/forum/yaf_postsm7221_3-1-6-MarketQuotingStrategy-bagh.aspx
							order.State = OrderStates.Done;

							//order.CancelTime = order.CancelTime ?? MarketTime;
							if (order.LastChangeTime == default(DateTime))
								order.LastChangeTime = order.Security.GetMarketTime();
						}
						else
						{
							// Предотвратить исполнение заявки при еще необновленном объеме
							// http://groups.google.ru/group/stocksharp/msg/a7d97814c859c722
							//
							if (state != OrderStates.Done)
								order.State = (OrderStates)state;
							else
							{
								throw new InvalidOperationException("Заявка {0} исполнилась полностью, но баланс не обновлен и равен {1}."
								                                    	.Put(order.TransactionId != 0 ? order.TransactionId : order.Id, order.Balance));
							}
						}
					}
				}

				ExportExtendedProperties(OrdersTable, order, row, func);
			}

			return true;
		}

		private bool TryCancelOrder(Order order)
		{
			if (order == null)
				throw new ArgumentNullException("order");

			// http://stocksharp.com/forum/yaf_postsm6906_Izmienieniie-Order-Balance-poslie-togho--kak-zaiavka-poluchila-OrderStates--Done.aspx#post6906
			// если баланс в заявке меньше 2-ух, то можно быть уверенным, что он больше не обновиться для отмененной заявки
			if (CancelOrderInstantly || order.Balance <= 1)
			{
				order.State = OrderStates.Done;
				//order.CancelTime = MarketTime;
				order.LastChangeTime = LoggingHelper.Now;
				return true;
			}

			return false;
		}

		private static void ExportExtendedProperties(DdeTable table, IExtendableEntity entity, IList<object> row, Func<DdeTableColumn, object> func)
		{
			if (table == null)
				throw new ArgumentNullException("table");

			if (entity == null)
				throw new ArgumentNullException("entity");

			if (row == null)
				throw new ArgumentNullException("row");

			if (func == null)
				throw new ArgumentNullException("func");

			if (table.Columns.ExtendedColumns.Count > 0)
			{
				if (entity.ExtensionInfo == null)
					entity.ExtensionInfo = new Dictionary<object, object>();

				table.Columns.ExtendedColumns.ForEach(c =>
				{
					var value = func(c);
					entity.ExtensionInfo[c] = QuikDdeFormatter.Get(value, c);
				});

				entity.RaiseExtensionInfoChanged();
			}
		}

		/// <summary>
		/// Зарегистрировать заявку на бирже.
		/// </summary>
		/// <param name="order">Заявка, содержащая информацию для регистрации.</param>
		protected override void OnRegisterOrder(Order order)
		{
			RegisterTransaction(order.CreateRegisterTransaction());
		}

		/// <summary>
		/// Перерегистрировать заявку на бирже.
		/// </summary>
		/// <param name="oldOrder">Старая заявка, которую нужно перерегистрировать.</param>
		/// <param name="newOrder">Информация о новой заявке.</param>
		protected override void OnReRegisterOrder(Order oldOrder, Order newOrder)
		{
			//Quik не поддерживает Move с MODE=1 для Единой Денежной Позиции.
			//http://quik.ru/forum/import/57855/57855/
			//Поэтому делаем Cancel, потом Register
			if (oldOrder.Security.ExchangeBoard.IsSupportAtomicReRegister && !IsCommonMonetaryPosition)
			{
				InitNewOrder(newOrder);

				RegisterTransaction(oldOrder.CreateMoveTransaction(newOrder));

				if (!IsAsyncMode)
				{
					if (newOrder.State != OrderStates.Failed)
					{
						// http://quik.ru/forum/import/49508/49518/
						if (oldOrder.State == OrderStates.Active)
						{
							if (TryCancelOrder(oldOrder))
								RaiseOrderChanged(oldOrder);
						}
					}
				}
			}
			else
				base.OnReRegisterOrder(oldOrder, newOrder);
		}

		/// <summary>
		/// Отменить заявку на бирже.
		/// </summary>
		/// <param name="order">Заявка, которую нужно отменять.</param>
		protected override void OnCancelOrder(Order order)
		{
			RegisterTransaction(order.CreateCancelTransaction());

			if (!IsAsyncMode)
				TryCancelOrder(order);
		}

		/// <summary>
		/// Зарегистрировать транзакцию.
		/// </summary>
		/// <param name="transaction">Транзакция.</param>
		public void RegisterTransaction(Transaction transaction)
		{
			if (transaction == null)
				throw new ArgumentNullException("transaction");

			if (transaction.GetInstruction<long>(Transaction.TransactionId) != 0)
				throw new ArgumentException();

			var transactionId = transaction.TryGetTransactionId();
			
			if (transactionId == 0)
				transactionId = TransactionIdGenerator.GetNextId();

			if (transactionId <= 0 || transactionId > uint.MaxValue)
				throw new InvalidOperationException("Номер транзакции {0} вне допустимого диапазона.".Put(transactionId));

			FormatTransaction.SafeInvoke(transaction.SetTransactionId(transactionId));

			foreach (var order in transaction.Orders)
				_orderTransactions.Add(order, transaction);

			_transactions.Add(transactionId, transaction);

			if (IsAsyncMode)
				Api.SendAsyncTransaction(transaction.ToString());
			else
			{
				Exception error = null;

				// http://stocksharp.com/forum/yaf_postst2247_Oshibka-pri-kotirovanii--sinkhronnyie-tranzaktsii.aspx
				//ProcessEvents(() =>
				//{
				var order = transaction.Orders.LastOrDefault();

				if (order == null)
					throw new ArgumentException("Транзакция не ассоциирована ни с одной заявкой.", "transaction");

				using (order.BeginUpdate())
				{
					var isReRegistering = transaction.TransactionType == TransactionTypes.ReRegister;
					var isRegistering = transaction.TransactionType == TransactionTypes.Register || isReRegistering;

					var apiMessage = "";

					try
					{
						long orderId;
						uint transId;
						OrderStatus status;

						var transactionTxt = transaction.ToString();

						Api.SendSyncTransaction(transactionTxt, out status, out transId, out orderId, out apiMessage);

						var isMatchOrCancel = order.ExecutionCondition == OrderExecutionConditions.MatchOrCancel;

						if ((!isMatchOrCancel && status != OrderStatus.Accepted) || (isMatchOrCancel && !TransactionHelper.IfFOKCancelMessage(apiMessage) && orderId == 0))
							throw new Exception("Транзакция '{0}' не была зарегистрирована. Причина '{1}'.".Put(transactionTxt, apiMessage));

						order.Status = status;
						order.Messages.Add(apiMessage);

						if (isRegistering)
							AssignOrderId(order, orderId, apiMessage, isReRegistering, Codes.Success, null);
					}
					catch (Exception ex)
					{
						var apiEx = ex as ApiException;

						if (isRegistering)
							AssignOrderId(order, 0, apiMessage, isReRegistering, apiEx != null ? apiEx.Code : Codes.Failed, ex);
						else
						{
							var exception = apiEx ?? new ApiException(Codes.Failed, apiMessage);
							RaiseOrderFailed(order, exception);
						}

						error = ex;
					}
				}
				//});

				if (error != null)
					throw error;
			}
		}

		private void AssignOrderId(Order order, long orderId, string message, bool isChanging, Codes code, Exception exception)
		{
			if (order == null)
				throw new ArgumentNullException("order");

			var id = GetOrderId(order, orderId, message, isChanging, code, exception);

			//Нулевой объём возможен при перерегистрации.
			//В этом случае ждём информации по потоку данных.
			if (id > 0 && order.Volume != 0)
			{
				GetOrder(order.Security, id, key => order, o =>
				{
					using (o.BeginUpdate())
					{
						if (o.State != OrderStates.None)
							return false;

						o.Id = id;

						if (TransactionHelper.IfFOKCancelMessage(message))
						{
							o.State = OrderStates.Done;
							//o.CancelTime = MarketTime
							o.LastChangeTime = o.Security.GetMarketTime();
						}
						else
							o.State = OrderStates.Active;

						return true;
					}
				});
			}
		}

		private long GetOrderId(Order order, long orderId, string message, bool isChanging, Codes code, Exception exception)
		{
			long id = 0;

			if (isChanging)
			{
				if (exception == null)
				{
					var matches = _changeOrdersRegex.Matches(message);

					if (matches.Count != 2)
						exception = new InvalidOperationException("Сервер для транзакции '{0}' вернул неправильное сообщение '{1}' по передвинутым заявкам.".Put(_transactions.TryGetValue(order.TransactionId), message));
					else
						id = matches[0].Groups["order"].Value.To<long>();
				}
			}
			else
				id = orderId;

			if (id == 0)
			{
				if (exception == null)
					exception = new ApiException(code, message);

				RaiseOrderFailed(order, exception);
			}

			return id;
		}

		/// <summary>
		/// Начать получать котировки (стакан) по инструменту.
		/// Значение котировок можно получить через метод <see cref="ITrader.GetMarketDepth"/>.
		/// </summary>
		/// <param name="security">Инструмент, по которому необходимо начать получать котировки.</param>
		protected override void OnRegisterMarketDepth(Security security)
		{
			var terminal = GetTerminal();

			if (!terminal.IsQuotesOpened(security))
				terminal.OpenQuotes(security);

			terminal.StartDde(security);
		}

		/// <summary>
		/// Остановить получение котировок по инструменту.
		/// </summary>
		/// <param name="security">Инструмент, по которому необходимо остановить получение котировок.</param>
		protected override void OnUnRegisterMarketDepth(Security security)
		{
			GetTerminal().StopDde(security);
		}

		/// <summary>
		/// Начать получать новую информацию (например, <see cref="Security.LastTrade"/> или <see cref="Security.BestBid"/>) по инструменту.
		/// </summary>
		/// <param name="security">Инструмент, по которому необходимо начать получать новую информацию.</param>
		protected override void OnRegisterSecurity(Security security)
		{
			GetTerminal().RegisterSecurity(security);
		}

		/// <summary>
		/// Остановить получение новой информации.
		/// </summary>
		/// <param name="security">Инструмент, по которому необходимо остановить получение новой информации.</param>
		protected override void OnUnRegisterSecurity(Security security)
		{
			GetTerminal().UnRegisterSecurity(security);
		}

		/// <summary>
		/// Начать получать сделки (тиковые данные) по инструменту. Новые сделки будут приходить через
		/// событие <see cref="ITrader.NewTrades"/>.
		/// </summary>
		/// <param name="security">Инструмент, по которому необходимо начать получать сделки.</param>
		protected override void OnRegisterTrades(Security security)
		{
			GetTerminal().RegisterTrades(security);
		}

		/// <summary>
		/// Остановить получение сделок (тиковые данные) по инструменту.
		/// </summary>
		/// <param name="security">Инструмент, по которому необходимо остановить получение сделок.</param>
		protected override void OnUnRegisterTrades(Security security)
		{
			GetTerminal().UnRegisterTrades(security);
		}

		/// <summary>
		/// Запустить экспорт данных из торговой системы в программу (получение портфелей, инструментов, заявок и т.д.).
		/// </summary>
		public override void StartExport()
		{
			StartDdeServer();
			GetTerminal().StartDde();

			base.StartExport();
		}

		/// <summary>
		/// Остановить экспорт данных из торговой системы в программу, запущенный через <see cref="ITrader.StartExport"/>.
		/// </summary>
		public override void StopExport()
		{
			GetTerminal().StopDde();

			base.StopExport();
		}

		/// <summary>
		/// Перезапустить экспорт данных из торговой системы через методы <see cref="ITrader.StopExport"/> и <see cref="ITrader.StartExport"/>.
		/// </summary>
		public override void ReStartExport()
		{
			StartDdeServer();
			GetTerminal().ReStartDde();
		}

		/// <summary>
		/// Запустить экспорт данных из торговой системы в программу по таблицам, указанных параметром ddeTables.
		/// </summary>
		/// <example><code>// запускаем экспорт по таблице инструментов и заявкам.
		/// _trader.StartExport(_trader.SecuritiesTable, _trader.OrdersTable);</code></example>
		/// <param name="ddeTables">Таблицы, для которых необходимо запустить экспорт через DDE.</param>
		public void StartExport(IEnumerable<DdeTable> ddeTables)
		{
			StartDdeServer();
			GetTerminal().StartDde(ddeTables);

			base.StartExport();
		}

		/// <summary>
		/// Остановить экспорт данных из торговой системы в программу по таблицам, указанных параметром ddeTables.
		/// </summary>
		/// <param name="ddeTables">Таблицы, для которых необходимо остановить экспорт через DDE.</param>
		public void StopExport(IEnumerable<DdeTable> ddeTables)
		{
			GetTerminal().StopDde(ddeTables);

			if (!IsExportStarted)
				base.StopExport();
		}

		/// <summary>
		/// Запустить экспорт данных из торговой системы в программу для произвольной таблицы, зарегистрированной в <see cref="QuikTrader.CustomTables"/>.
		/// </summary>
		/// <param name="customTable">Описание DDE экспорта произвольной таблицы.</param>
		public void StartExport(DdeCustomTable customTable)
		{
			StartDdeServer();
			GetTerminal().StartDde(customTable);

			base.StartExport();
		}

		/// <summary>
		/// Остановить экспорт данных из торговой системы в программу для произвольной таблицы, зарегистрированной в <see cref="QuikTrader.CustomTables"/>.
		/// </summary>
		/// <param name="customTable">Описание DDE экспорта произвольной таблицы.</param>
		public void StopExport(DdeCustomTable customTable)
		{
			GetTerminal().StopDde(customTable);

			if (!IsExportStarted)
				base.StopExport();
		}

		/// <summary>
		/// Запустить экспорт данных таблицы из торговой системы в программу.
		/// </summary>
		/// <param name="caption">Название таблицы, для которой нужно запустить экспорт.</param>
		public void StartExport(string caption)
		{
			StartDdeServer();
			GetTerminal().StartDde(caption);

			base.StartExport();
		}

		/// <summary>
		/// Остановить экспорт данных таблицы из торговой системы в программу.
		/// </summary>
		/// <param name="caption">Название таблицы, для которой нужно запустить экспорт.</param>
		public void StopExport(string caption)
		{
			GetTerminal().StopDde(caption);

			if (!IsExportStarted)
				base.StopExport();
		}

		private void StartDdeServer()
		{
			if (!QuikDdeServer.IsRegistered)
				QuikDdeServer.Start();
		}

		private void OnOrderReply(Modes mode, uint transId, long orderId, string classCode, string secCode, double price, int balance, int volume, OrderDirections direction, OrderStates state)
		{
			this.AddDebugLog("Order: Mode {0} transId {1} orderId {2} classCode {3} secCode {4} price {5} balance {6} volume {7} direction {8} state {9}", mode, transId, orderId, classCode, secCode, price, balance, volume, direction, state);
		}

		private void OnTradeReply(Modes mode, long tradeId, long orderId, string classCode, string secCode, double price, int balance, int volume, OrderDirections direction)
		{
			this.AddDebugLog("Trade: Mode {0} tradeId {1} orderId {2} classCode {3} secCode {4} price {5} balance {6} volume {7} direction {8}", mode, tradeId, orderId, classCode, secCode, price, balance, volume, direction);
		}

		private void OnTransactionReply(uint transactionId, Codes replyCode, Codes extendedCode, OrderStatus status, long orderId, string message)
		{
			this.AddDebugLog("Order: transId {0} replyCode {1} extendedCode {2} status {3} orderId {4} message {5}", transactionId, replyCode, extendedCode, status, orderId, message);

			if (!IsAsyncMode)
				return;

			ProcessEvents(() =>
			{
				var builder = _transactions.TryGetValue(transactionId);

				if (builder == null)
					throw new InvalidOperationException("Информация для транзакции {0} не найдена.".Put(transactionId));

				if (builder.TransactionType == TransactionTypes.CancelGroup)
					return;

				if (builder.TransactionType == TransactionTypes.Register && extendedCode == Codes.Success && orderId == 0)
					extendedCode = Codes.Failed;

				var isCancelFailed = builder.TransactionType == TransactionTypes.Cancel && status != OrderStatus.Accepted;

				if (isCancelFailed)
					extendedCode = Codes.Failed;

				ApiException exception = null;

				if (extendedCode != Codes.Success)
					exception = new ApiException(extendedCode, message);

				var order = builder.Orders[0];

				using (order.BeginUpdate())
				{
					order.Messages.Add(message);

					if (!isCancelFailed)
						order.Status = status;
				}

				switch (builder.TransactionType)
				{
					case TransactionTypes.Register:
						ProcessFokTransaction(order, orderId, message, false, replyCode, exception);
						break;
					case TransactionTypes.ReRegister:
						var newOrder = builder.Orders[1];
						
						ProcessFokTransaction(newOrder, orderId, message, true, replyCode, exception);

						using (newOrder.BeginUpdate())
						{
							if (newOrder.State != OrderStates.Failed)
								newOrder.Status = OrderStatus.Accepted;
						}

						break;
					case TransactionTypes.Cancel:
						if (exception != null)
							RaiseOrderFailed(order, exception);
						break;
					default:
						throw new NotSupportedException("Неподдерживаемый тип транзакции {0}.".Put(builder.TransactionType));
				}
			});
		}

		private void ProcessFokTransaction(Order order, long orderId, string message, bool isChanging, Codes code, Exception exception)
		{
			if (order.Id != 0)
				return;

			orderId = GetOrderId(order, orderId, message, isChanging, code, exception);

			if (orderId != 0 && TransactionHelper.IfFOKCancelMessage(message))
			{
				GetOrder(order.Security, orderId, id => order, o =>
				{
					using (o.BeginUpdate())
					{
						o.State = OrderStates.Done;
						o.LastChangeTime = o.Security.GetMarketTime();
					}
					return true;
				});
			}
		}

		/// <summary>
		/// Получить транзакцию по идентификатору.
		/// </summary>
		/// <param name="id">Идентификатор транзакции.</param>
		/// <returns>Транзакция.</returns>
		public Transaction GetTransaction(long id)
		{
			return _transactions.TryGetValue(id);
		}

		/// <summary>
		/// Получить транзакции, связанные с заявкой (например, на регистрацию и снятие).
		/// </summary>
		/// <param name="order">Заявка, для которой необходимо получить ее транзакции.</param>
		/// <returns>Транзакции.</returns>
		public IEnumerable<Transaction> GetTransactions(Order order)
		{
			if (order == null)
				throw new ArgumentNullException("order");

			return _orderTransactions.TryGetValue(order);
		}

		/// <summary>
		/// Сохранить настройки.
		/// </summary>
		/// <param name="storage">Хранилище настроек.</param>
		public override void Save(SettingsStorage storage)
		{
			base.Save(storage);

			storage.SetValue("Path", Path);
			storage.SetValue("DllName", DllName);
			storage.SetValue("IsAsyncMode", IsAsyncMode);
			storage.SetValue("DdeServer", DdeServer);

			storage.SetValue("SecuritiesTable", SecuritiesTable);
			storage.SetValue("TradesTable", TradesTable);
			storage.SetValue("OrdersTable", OrdersTable);
			storage.SetValue("StopOrdersTable", StopOrdersTable);
			storage.SetValue("MyTradesTable", MyTradesTable);
			storage.SetValue("QuotesTable", QuotesTable);
			storage.SetValue("EquityPortfoliosTable", EquityPortfoliosTable);
			storage.SetValue("EquityPositionsTable", EquityPositionsTable);
			storage.SetValue("DerivativePortfoliosTable", DerivativePortfoliosTable);
			storage.SetValue("DerivativePositionsTable", DerivativePositionsTable);
			storage.SetValue("CurrencyPortfoliosTable", CurrencyPortfoliosTable);
		}

		/// <summary>
		/// Загрузить настройки.
		/// </summary>
		/// <param name="storage">Хранилище настроек.</param>
		public override void Load(SettingsStorage storage)
		{
			base.Load(storage);

			Path = storage.GetValue<string>("Path");
			DllName = storage.GetValue<string>("DllName");
			IsAsyncMode = storage.GetValue<bool>("IsAsyncMode");
			DdeServer = storage.GetValue<string>("DdeServer");

			SecuritiesTable = storage.GetValue<DdeTable>("SecuritiesTable");
			TradesTable = storage.GetValue<DdeTable>("TradesTable");
			OrdersTable = storage.GetValue<DdeTable>("OrdersTable");
			StopOrdersTable = storage.GetValue<DdeTable>("StopOrdersTable");
			MyTradesTable = storage.GetValue<DdeTable>("MyTradesTable");
			QuotesTable = storage.GetValue<DdeTable>("QuotesTable");
			EquityPortfoliosTable = storage.GetValue<DdeTable>("EquityPortfoliosTable");
			EquityPositionsTable = storage.GetValue<DdeTable>("EquityPositionsTable");
			DerivativePortfoliosTable = storage.GetValue<DdeTable>("DerivativePortfoliosTable");
			DerivativePositionsTable = storage.GetValue<DdeTable>("DerivativePositionsTable");
			CurrencyPortfoliosTable = storage.GetValue<DdeTable>("CurrencyPortfoliosTable");
		}

		private void DisposeDdeServer()
		{
			lock (_quikDdeServerLock)
			{
				if (_quikDdeServer == null)
					return;

				_quikDdeServer.Poke -= OnPoke;
				_quikDdeServer.Error -= RaiseProcessDataError;
				_quikDdeServer.Dispose();
				_quikDdeServer = null;
			}
		}

		/// <summary>
		/// Освободить занятые ресурсы. В частности, отключиться от торговой системы через <see cref="BaseTrader.Disconnect"/>,
		/// выключить DDE сервер.
		/// </summary>
		protected override void DisposeManaged()
		{
			try
			{
				GetTerminal().StopActiveDdeExport();
			}
			catch (Exception ex)
			{
				RaiseProcessDataError(ex);
			}

			DisposeDdeServer();

			if (_api != null)
			{
				if (Api.IsDllConnected() == Codes.DllConnected)
					base.DisposeManaged();
			}

			Terminal = null;

			Interlocked.Decrement(ref _counter);
		}
	}
}
